﻿using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WinAction.Hook
{
	public static class KeyBoardHook
	{
		[StructLayout(LayoutKind.Sequential)]
		private class KeyBoardHookStruct
		{
			public int vkCode;
			public int scanCode;
			public int flags;
			public int time;
			public int dwExtraInfo;
		}

		//设置钩子 
		[DllImport("user32.dll")]
		private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

		//抽掉钩子 
		[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
		private static extern bool UnhookWindowsHookEx(int idHook);

		//调用下一个钩子 
		[DllImport("user32.dll")]
		private static extern int CallNextHookEx(int idHook, int nCode, int wParam, IntPtr lParam);

		// 当前模块句柄
		[DllImport("kernel32.dll")]
		private static extern IntPtr GetModuleHandle(string name);

		// 判断指定键是否按下
		[DllImport("user32.dll")]
		private static extern ushort GetAsyncKeyState(int vkey);

		private delegate int HookProc(int nCode, int wParam, IntPtr lParam);
		private static HookProc KeyBoardHookProcedure;
		private static object locker = new object();
		// 当前已经注册的事件
		private static ConcurrentDictionary<string, Action<Keys, KeyStatus>> eventDic = new ConcurrentDictionary<string, Action<Keys, KeyStatus>>();

		private static volatile int hHook = 0;

		/// <summary>
		/// 判断指定的键是否按下
		/// </summary>
		/// <param name="key">目标键</param>
		/// <returns></returns>
		public static bool KeyIsPress(Keys key)
		{
			return (GetAsyncKeyState((int)key) & 0x8000) != 0;
		}

		public static bool LCtrlKeyIsPress() => KeyIsPress(Keys.LControlKey);

		public static bool RCtrlKeyIsPress() => KeyIsPress(Keys.RControlKey);

		public static bool CtrlKeyIsPress() => LCtrlKeyIsPress() || RCtrlKeyIsPress();

		public static bool LShiftKeyIsPress() => KeyIsPress(Keys.LShiftKey);

		public static bool RShiftKeyIsPress() => KeyIsPress(Keys.RShiftKey);

		public static bool ShiftKeyIsPress() => LShiftKeyIsPress() || RShiftKeyIsPress();

		public static bool LAltKeyIsPress() => KeyIsPress(Keys.LMenu);

		public static bool RAltKeyIsPress() => KeyIsPress(Keys.RMenu);

		public static bool AltKeyIsPress() => LAltKeyIsPress() || RAltKeyIsPress();

		public static bool LWinKeyIsPress() => KeyIsPress(Keys.LWin);

		public static bool RWinKeyIsPress() => KeyIsPress(Keys.RWin);

		public static bool WinKeyIsPress() => LWinKeyIsPress() || RWinKeyIsPress();

		/// <summary>
		/// 检测全部键是否按下（如果在钩子中调用，但是在钩子中首次触发的普通按键，
		/// 会在钩子执行完毕才会被置位按下状态，该函数会在下一个周期才能被检测出来）
		/// </summary>
		/// <param name="keys">键数组</param>
		/// <returns>是否按下</returns>
		public static bool DetectKeysAllDown(params Keys[] keys)
		{
			foreach (Keys key in keys.ToList().Distinct())
			{
				if (key == Keys.Control || key == Keys.ControlKey)
				{
					if (!CtrlKeyIsPress())
					{
						return false;
					}
					continue;
				}

				if (key == Keys.ShiftKey || key == Keys.Shift)
				{
					if (!ShiftKeyIsPress())
					{
						return false;
					}
					continue;
				}

				if (key == Keys.Alt || key == Keys.Menu)
				{
					if (!AltKeyIsPress())
					{
						return false;
					}
					continue;
				}

				if (!KeyIsPress(key))
				{
					return false;
				}
			}

			return true;
		}

		/// <summary>
		/// 检测全部键是否按下（建议在钩子中调用）
		/// </summary>
		/// <param name="triggerKey">在钩子中触发的按键</param>
		/// <param name="keys">待检测的按键</param>
		/// <returns>是否按下</returns>
		public static bool DetectKeysAllDownWithTriggerKey(Keys triggerKey, params Keys[] keys)
		{
			foreach (Keys key in keys.ToList().Distinct())
			{
				if (DetectKeysAllDown(key) || triggerKey == key)
				{
					continue;
				}

				return false;
			}

			return true;
		}


		/// <summary>
		/// 开始hock
		/// </summary>
		/// <param name="getSpecialKeys">获取特殊功能键是否点击的委托</param>
		private static void StartHook()
		{
			lock (locker)
			{
				if (hHook == 0)
				{
					KeyBoardHookProcedure = new HookProc(KeyBoardHookProc);
					hHook = SetWindowsHookEx((int)HookType.WH_KEYBOARD_LL, KeyBoardHookProcedure,
						GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName), 0);
					if (hHook == 0)
					{
						ClearHook();
						return;
					}
				}
			}
		}

		/// <summary>
		/// 取消钩子事件
		/// </summary>
		private static void ClearHook()
		{
			lock (locker)
			{
				if (hHook != 0)
				{
					UnhookWindowsHookEx(hHook);
					hHook = 0;
				}
			}
		}

		/// <summary>
		/// 注册一个键盘监听事件
		/// </summary>
		/// <param name="keyEvent">键盘事件</param>
		/// <returns>注册事件的ID</returns>
		public static string RegisterKeyHookEvent(Action<Keys, KeyStatus> keyEvent)
		{
			if (keyEvent == null)
			{
				throw new NullReferenceException("待注册的键盘事件不可为null");
			}
			string id = Guid.NewGuid().ToString("N");
			if (eventDic.TryAdd(id, keyEvent))
			{
				StartHook();
				return id;
			}

			return null;
		}

		/// <summary>
		/// 取消注册键盘事件
		/// </summary>
		/// <param name="eventId">事件ID</param>
		public static bool UnRegisterKeyHookEvent(string eventId)
		{
			Action<Keys, KeyStatus> keyEvent;
			bool res = eventDic.TryRemove(eventId, out keyEvent);
			if (eventDic.Count <= 0)
			{
				ClearHook();
			}

			return res;
		}

		/// <summary>
		/// 处理键盘钩子事件
		/// </summary>
		/// <param name="nCode">是否成功</param>
		/// <param name="wParam">事件类型，对应注册传入的事件类型</param>
		/// <param name="lParam">对应钩子的详情指针</param>
		/// <returns></returns>
		private static int KeyBoardHookProc(int nCode, int wParam, IntPtr lParam)
		{
			if (nCode >= 0)
			{
				KeyBoardHookStruct kbh = (KeyBoardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyBoardHookStruct));
				Keys k = (Keys)Enum.Parse(typeof(Keys), kbh.vkCode.ToString());
				KeyStatus keyStatus = kbh.flags >= 64 ? KeyStatus.KEY_UP : KeyStatus.KEY_DOWN;
				foreach (var eventPair in eventDic)
				{
					eventPair.Value?.Invoke(k, keyStatus);
				}
			}
			return CallNextHookEx(hHook, nCode, wParam, lParam);
		}
	}

	public enum KeyStatus
	{
		KEY_DOWN,
		KEY_UP
	}
}
